home *** CD-ROM | disk | FTP | other *** search
Text File | 1985-07-26 | 41.8 KB | 2,177 lines |
- /*
- * This programmable integer arithmetic calculator features the following:
- *
- * + uses most operators found in the C language
- * + 'if-else' and 'while-break' flow control constructs
- * + "edit", "list", "load", "save", "run" and "exit" commands
- * + built-in line oriented editor
- * + built-in functions (expandable)
- *
- * The program is composed of 4 modules, the main keyboard command
- * interpreter including all of the command handlers; the token parser;
- * the statement parser; and the p-code program interpreter.
- *
- * COMMAND INTERPRETER
- *
- * The command interpreter prompts for a line of input from the console,
- * hands it off to the parser and then to the p-code interpreter
- * to be executed. The result of the STATEMENT is then printed using the
- * current number radix (see BUILT-IN FUNCTION base(), below).
- *
- * EDITOR
- *
- * The line editor is similar in concept to the editor available in MS-BASIC.
- * By ignoring TAB codes in program text, it was possible to keep the editor
- * code extremely simple. Editor commands are:
- *
- * ^E ^X - display previous/next line in program buffer (up/down)
- * ^S ^D - move cursor left/right
- * ^W - "window" 22 lines of program buffer around current line
- * ^C - enter character-insert mode, terminated with CR or LF
- * ^V - enter line-insert mode, terminated with CR or LF
- * ^B - delete character under cursor
- * ^Y - delete current line
- *
- * Since the editor requires that a blank line always exist at the end of
- * the buffer, there is no need for a line APPEND command.
- *
- * LANGUAGE SYNTAX
- *
- * CONSTANTS
- *
- * Constants may be either decimal, hexadecimal with a leading "0x" like
- * in C or octal with a leading "0". Strings are delimited with a quote,
- * but unlike C they may be terminated with a newline instead of a close
- * quote. All the standard character escapes ('\n', '\r', etc.) may be
- * used within strings.
- *
- * VARIABLES
- *
- * Only 52 global variables are available, these are referenced by a SINGLE
- * lower or upper case letter (a-z and A-Z).
- *
- * OPERATORS
- *
- * Most standard C operators are available:
- *
- * + - / * % ! ~ & && | || ^ << >> < <= > >= == !=
- * = , ( )
- *
- * The address operator is "@" instead of "&" and may be used only in front
- * of a variable reference. All other operators behave as expected.
- *
- * EXPRESSIONS
- *
- * Parenthesized expressions are allowed. An expression may be terminated
- * with either a newline or a semicolon.
- *
- * STATEMENTS
- *
- * Statements may be either an expression or a list of expressions delimited
- * with "{" and "}" like in C. The "if-else" and "while-break" constructs
- * behave as in C.
- *
- * BUILT-IN FUNCTIONS
- *
- * Since this program was designed to be expandable, it currently offers
- * only a few built-in functions. These are:
- *
- * new() - erase the program buffer
- * edit(n) - envoke the editor at line "n"
- * list() - list the program buffer to CON:
- * save(s) - save the program buffer in a file named "s"
- * load(s) - load program buffer from file named "s"
- * stop(n) - stop the program and print the integer "n"
- * exit() - exit to CP/M
- * base(n) - change output number base
- * printf(..) - just like the standard printf() found in C
- * nl() - output a newline to CON:
- * putn(n) - output integer n in current output number base
- * getn(v) - read an integer value from CON: to the address at v
- * run(s) - chain to another program
- * debug(n) - enable/disable calculator debug print statements
- *
- * OPERATION
- *
- * The program uses two buffers, one contains the program source lines
- * (char *Prog[]) and the other a tokenized, RPN representation of the
- * source (struct Opstk[]). The token parser and statement parser convert
- * the source buffer into one-character tokens and stack them in Reverse
- * Polish Notation (RPN) onto the Operator/Operand stack (Opstk). The
- * p-code interpreter then scans through the stack and performs each
- * operation in sequence. Results of operations are kept on a "value"
- * stack (int Valstk[]). All built-in functions must maintain the integrity
- * of this stack, since no stack frame exists to restore the stack pointer
- * on exit from the function.
- */
- #include <stdio.h>
- #define DEBUG 1
-
- /*
- * Tokens
- */
- #define T_EOL '.'
- #define T_SEMICOLON ';'
- #define T_EOF 'z'
- #define T_POP 'p'
- #define T_CONST 'C'
- #define T_STRING 'S'
- #define T_SYMBOL 'Y'
- #define T_LBRACE '{'
- #define T_RBRACE '}'
- #define T_LPAREN '('
- #define T_RPAREN ')'
- #define T_COMMA ','
- #define T_ASSIGN '='
- #define T_POINT '$'
- #define T_ADDR '@'
- #define T_MUL '*'
- #define T_DIV '/'
- #define T_MOD '%'
- #define T_ADD '+'
- #define T_SUB '-'
- #define T_NEG '_'
-
- #define T_SHL 'L'
- #define T_SHR 'R'
- #define T_LT '<'
- #define T_LE 'l'
- #define T_GT '>'
- #define T_GE 'g'
- #define T_EQ 'q'
- #define T_NE 'n'
-
- #define T_NOT '~'
- #define T_AND '&'
- #define T_XOR '^'
- #define T_IOR '|'
- #define T_LNOT '!'
- #define T_LAND 'a'
- #define T_LIOR 'o'
-
- #define T_FUNC 'F'
-
- #define T_IF 'i'
- #define T_ELSE 'e'
- #define T_WHILE 'w'
- #define T_BREAK 'b'
-
- /*
- * Program line buffer
- */
- #define MAXLINES 128 /* max length of a program */
- char *Prog[ MAXLINES ];
- int Progptr, Progtop; /* program current line and last line pointer */
- char Source; /* set when parsing from Prog[] buffer */
-
- /*
- * Default program file name
- */
- char Filenm[ 16 ];
-
- /*
- * Operator/Operand buffer - contains tokenized version of source lines.
- */
- #define MAXOPS 1024
- struct {
- char o_token;
- int o_value;
- } Opstk[ MAXOPS ];
- int Opptr; /* current p-code pointer */
- int Opsp; /* size of buffer */
-
- /*
- * Value (working) stack
- */
- #define MAXVALS 128
- int Valstk[ MAXVALS ];
- int Valsp; /* top of stack ptr */
-
- /* macro returns value on top of stack: */
- #define TOS (Valstk[Valsp-1])
-
- /*
- * Built-in Functions and jump table
- */
- #define MAXFUNCS 14
- int f_printf(), f_base(), f_nl(), f_putn(), f_getn(), f_run(), f_debug(),
- f_new(), f_edit(), f_list(), f_save(), f_load(), exit(), f_stop();
- struct _functab {
- char *f_name;
- int (*f_addr)();
- } Functab[ MAXFUNCS ];
-
- /*
- * Keyword lookup table
- */
- #define MAXKEYS 4
- struct _keytab {
- char *k_name;
- char k_value;
- } Keytab[ MAXKEYS ];
-
- /*
- * Symbol Table - symbols are referenced by a single letter (a-z or A-Z)
- */
- int Symbols[ 52 ];
-
- /*
- * String table
- */
- #define MAXSTRINGS 1024
- char *Strings;
- int Nextstr;
-
- /*
- * "if" and "while" stacks
- */
- #define MAXIFS 10
- #define MAXWHILES 10
- int Ifstk[ MAXIFS ], Whstk[ MAXWHILES ];
- char Ifsp, Whsp; /* top of stack ptrs */
-
- /*
- * Miscellaneous
- */
- int Level; /* current lexical level */
- int Parens; /* # of open parens (for error checking) */
- int Commas; /* # of commas encountered in statement (argument count-1) */
- char Token; /* current input token */
- int Value; /* and its value */
- #ifdef DEBUG
- char Debug; /* interpreter debug flag */
- #endif
- char Eol; /* set when end of line encountered */
- char Line[80]; /* input line, when not parsing from Prog[] buffer */
- char *Lineptr; /* points to next character in either Line[] or Prog[] */
- char *Ofmt; /* current output format (set by "base" command) */
- int Error; /* set if on error */
-
- char *skipws();
-
- /*************************************************************
- * MAIN PROGRAM *
- *************************************************************/
- main()
- {
- initialize();
- for ( ;; )
- {
- reset();
- prompt();
- if ( gets( Line ) )
- {
- /*
- * input line was not "run" - assume it's
- * a valid statement. Attempt to parse the
- * input line, generate pseudo-code and
- * evaluate it.
- */
- Source = 0;
- getoken();
- do
- statement();
- while ( !(Eol || Error) );
- if ( !Error )
- {
- evaluate();
- putresult( pop() );
- }
- }
- }
- }
-
- /*************************************************************
- * LEXICAL ANALYZER *
- *************************************************************/
- getoken()
- {
- /*
- * Lexical Analyzer. Gets next token from the input line
- * pointed to by "Lineptr" and advances "Lineptr" to next
- * character. If end of input line is encountered, the
- * "Eol" flag is set.
- */
- char *cp, buf[ 128 ];
- int i;
-
- if ( Error )
- goto done;
-
- if ( Eol )
- {
- /*
- * Found end of line, time to get a new line.
- */
- Eol = 0;
- if ( Source )
- {
- /*
- * We're executing a program. Get next line of
- * input from program buffer.
- */
- if ( Progptr == Progtop )
- /*
- * End of program buffer.
- */
- goto done;
- else
- Lineptr = Prog[ Progptr++ ];
- }
- else
- {
- /*
- * Immediate mode. Check if lexical end of
- * statement was not yet found.
- */
- if ( Level )
- {
- prompt();
- gets( Line );
- }
- Lineptr = Line;
- }
- #ifdef DEBUG
- if ( Debug )
- printf( "$%3d: %s\n", Progptr, Lineptr );
- #endif
- }
- /*
- * skip white space
- */
- Lineptr = skipws( Lineptr );
-
- if ( ! *Lineptr )
- {
- Eol = 1;
- Token = T_EOL;
- }
- else if ( *Lineptr == '0' )
- {
- /*
- * Check if it's a hex or octal constant
- */
- Token = T_CONST;
- ++Lineptr;
- if ( toupper( *Lineptr ) == 'X' )
- {
- ++Lineptr;
- for ( cp = buf; ishexdigit( *Lineptr ); )
- *cp++ = *Lineptr++;
- *cp = 0;
- sscanf( buf, "%x", &Value );
- }
- else if ( isdigit( *Lineptr ) )
- {
- for ( cp = buf; isoctdigit( *Lineptr ); )
- *cp++ = *Lineptr++;
- *cp = 0;
- sscanf( buf, "%o", &Value );
- }
- else
- Value = 0;
- }
- else if ( *Lineptr == '"' )
- {
- /*
- * It's a string constant. String constants are terminated
- * by either the second quote encountered, or end of line.
- * Value becomes the address of the string.
- */
- ++Lineptr;
- for ( cp = buf; *Lineptr && *Lineptr != '"'; )
- charescape( &cp );
- if ( *Lineptr )
- ++Lineptr;
- Value = *cp = 0;
- Token = T_STRING;
- /*
- * Check if string is duplicated somewhere in string table.
- */
- for ( cp=Strings; cp<Strings+Nextstr; cp += strlen(cp)+1 )
- {
- if ( ! strcmp( cp, buf ) )
- {
- Value = cp;
- break;
- }
- }
-
- if ( ! Value )
- {
- /*
- * String is unique - make a new entry in string
- * string table.
- */
- if ( (i = Nextstr + strlen( buf ) + 1) > MAXSTRINGS )
- err( "string space overflow" );
- else
- {
- Value = &Strings[ Nextstr ];
- strcpy( Value, buf );
- Nextstr = i;
- }
- }
- }
- else if ( isdigit( *Lineptr ) )
- {
- /*
- * It's a numeric constant, "Value" will be its value.
- */
- Token = T_CONST;
- for ( cp = buf; isdigit( *Lineptr ); )
- *cp++ = *Lineptr++;
- *cp = 0;
- Value = atoi( buf );
- }
- else if ( Value = isfunc() )
- {
- /*
- * It's a built-in function, "Value" will be the index
- * into the function jump table.
- */
- Token = T_FUNC;
- --Value;
- }
- else if ( Token = iskeyword() )
- ;
- else if ( Token = isoperator() )
- /*
- * It's a binary operator
- */
- ;
- else if ( isalpha( *Lineptr ) )
- {
- /*
- * It's a variable reference
- */
- Token = T_SYMBOL;
- if ( 'A'<=*Lineptr && *Lineptr<='Z' )
- Value = *Lineptr - 'A';
- else
- Value = (toupper( *Lineptr ) - 'A') + 26;
- ++Lineptr;
- }
- else
- {
- /*
- * Bad character in input line
- */
- err( "syntax error" );
- done:
- Eol = 1; /* make immediate mode commands give up */
- Source = 0; /* make run() give up */
- Token = T_EOF; /* make statement() give up */
- }
-
- return Token;
- }
-
- char *
- skipws( cp )
- char *cp;
- {
- while ( *cp==' ' || *cp=='\t' )
- ++cp;
- return cp;
- }
-
- charescape( cpp )
- char **cpp;
- {
- /*
- * Copy the next character from Lineptr into the string
- * pointed to by "cpp". If a '\' is found, translate the
- * following character(s) a la C.
- */
- char *cp, c;
- int i;
-
- cp = *cpp;
-
- if ( (c = *Lineptr++) == '\\' )
- {
- switch ( c = *Lineptr++ )
- {
- case 'b': *cp++ = '\b'; break;
- case 'n': *cp++ = '\n'; break;
- case 't': *cp++ = '\t'; break;
- case 'f': *cp++ = '\f'; break;
- case 'r': *cp++ = '\r'; break;
- case '0':
- case '1':
- sscanf( Lineptr-1, "%o", &i );
- Lineptr += 2;
- *cp++ = i;
- break;
- default: *cp++ = c;
- }
- }
- else
- *cp++ = c;
-
- *cpp = cp;
- }
-
- isfunc()
- {
- /*
- * Check if string pointed to by "Lineptr" is the name of a
- * built-function, return the function jump table index+1 if
- * so and bump "Lineptr" to next character.
- * Return 0 if not a function.
- */
- char *cp, *bp, buf[ 80 ];
- int funcno, i;
-
- /*
- * copy the name from input line buffer to a local buffer so
- * we can use it to make a proper comparison to function names.
- */
- for ( cp=Lineptr, bp=buf; isalpha( *cp ); )
- *bp++ = *cp++;
- *bp = 0;
-
- /*
- * compare it to all of the function names we know about.
- */
- for ( funcno = i = 0; i < MAXFUNCS; ++i )
- {
- if ( ! strcmp( buf, Functab[ i ].f_name ) )
- {
- funcno = i + 1;
- Lineptr = cp;
- break;
- }
- }
-
- return funcno;
- }
-
- iskeyword()
- {
- /*
- * Check if string pointed to by "Lineptr" is a keyword.
- * Return the keyword's token value and and bump "Lineptr"
- * to next character, or 0 if not a keyword.
- */
- char *cp, *bp, buf[ 80 ];
- char keyno;
- int i;
-
- /*
- * copy the name from input line buffer to a local buffer so
- * we can use it to make a proper comparison to keywords.
- */
- for ( cp=Lineptr, bp=buf; isalpha( *cp ); )
- *bp++ = *cp++;
- *bp = 0;
-
- /*
- * compare it to all of the keywords.
- */
- for ( keyno = i = 0; i < MAXKEYS; ++i )
- {
- if ( ! strcmp( buf, Keytab[ i ].k_name ) )
- {
- keyno = Keytab[ i ].k_value;
- Lineptr = cp;
- break;
- }
- }
-
- return keyno;
- }
-
- isoperator()
- {
- /*
- * Check if string pointed to by "Lineptr" is an operator,
- * return its token value and bump "Lineptr" to next character.
- */
- int tkn;
- char c;
-
- switch ( *Lineptr )
- {
- case ',':
- ++Commas;
- tkn = T_COMMA;
- break;
- case '=':
- if ( Lineptr[1] == '=' )
- {
- tkn = T_EQ;
- ++Lineptr;
- }
- else
- tkn = T_ASSIGN;
- break;
- case '!':
- if ( Lineptr[1] == '=' )
- {
- tkn = T_NE;
- ++Lineptr;
- }
- else
- tkn = T_LNOT;
- break;
- case '<':
- if ( (c = Lineptr[1]) == '<' )
- {
- tkn = T_SHL;
- ++Lineptr;
- }
- else if ( c == '=' )
- {
- tkn = T_LE;
- ++Lineptr;
- }
- else
- tkn = T_LT;
- break;
- case '>':
- if ( (c = Lineptr[1]) == '>' )
- {
- tkn = T_SHR;
- ++Lineptr;
- }
- else if ( c == '=' )
- {
- tkn = T_GE;
- ++Lineptr;
- }
- else
- tkn = T_GT;
- break;
- case '(':
- ++Parens;
- tkn = T_LPAREN;
- break;
- case ')':
- --Parens;
- tkn = T_RPAREN;
- break;
- case '&':
- if ( Lineptr[1] == '&' )
- {
- tkn = T_LAND;
- ++Lineptr;
- }
- else
- tkn = T_AND;
- break;
- case '|':
- if ( Lineptr[1] == '|' )
- {
- tkn = T_LIOR;
- ++Lineptr;
- }
- else
- tkn = T_IOR;
- break;
- default:
- if ( instr( *Lineptr, ";@{}*/%+-^~" ) )
- tkn = *Lineptr;
- else
- tkn = 0;
- }
-
- if ( tkn )
- ++Lineptr;
-
- return tkn;
- }
-
- skipnl()
- {
- while ( Token==T_EOL )
- getoken();
- }
-
- /*************************************************************
- * STATEMENT PARSER *
- **************************************************************/
- statement()
- {
- /*
- * Parse a statement. The BNF for statements is:
- * <statement> := <expression> <eol> |
- * '{' <statement-list> '}'
- * and, of course:
- * <statement-list> := <eol> |
- * <statement> <eol> |
- * <statement-list> <statement> <eol>
- * finally:
- * <eol> := '\n' |
- * ';' |
- * ';' '\n'
- */
- start:;
-
- switch ( Token )
- {
- case T_EOL:
- getoken();
- goto start;
- case T_SEMICOLON:
- getoken();
- skipnl();
- case T_EOF:
- break;
- case T_IF:
- ++Level;
- doif();
- if ( Token!=T_EOF )
- --Level;
- break;
- case T_ELSE:
- doelse();
- break;
- case T_WHILE:
- ++Level;
- dowhile();
- if ( Token!=T_EOF )
- --Level;
- break;
- case T_BREAK:
- dobreak();
- break;
- case T_LBRACE:
- ++Level;
- getoken();
- do
- statement();
- while ( !Error && Token != T_RBRACE && Token!=T_EOF );
-
- if ( Token!=T_EOF )
- {
- getoken();
- --Level;
- }
- break;
- case T_RBRACE:
- if ( !Level )
- err( "'{' missing" );
- break;
- case T_RPAREN:
- if ( Parens<0 )
- err( "'(' missing" );
- break;
- default:
- expression();
- generate( T_POP, 0 );
- }
-
- if ( Token == T_EOF && Level )
- err( "incomplete statement" );
- }
-
- doif()
- {
- /*
- * Parse an "if" statement:
- * 'if' <expression> <statement>
- */
-
- getoken();
- expression();
- /*
- * Save current operator stack pointer for backpatching later.
- * This is pushed onto a stack so that it will be available for
- * possible future "else" statements.
- */
- pushif( Opsp );
- /*
- * generate a "jump if value on stack is zero" code.
- */
- generate( T_IF, -1 );
- /*
- * parse the <statement> part, then backpatch the above
- * "jump if zero" opcode to point to next program line.
- */
- statement();
- skipnl();
- if ( Token == T_ELSE )
- doelse();
- Opstk[ popif() ].o_value = Opsp;
- }
-
- doelse()
- {
- /*
- * Parse an "else" statement.
- * 'if' <expression> <statement> 'else' <statement>
- */
- int p;
-
- /*
- * generate a "jump to end of if-else" opcode, then backpatch
- * the "jump if zero" opcode generated by doif() to point to
- * here.
- */
- getoken();
- p = popif();
- pushif( Opsp );
- generate( T_WHILE, -1 );
- Opstk[ p ].o_value = Opsp;
- statement();
- }
-
- dowhile()
- {
- /*
- * Parse a "while" statement.
- * 'while' <expression> <statement>
- */
- int p;
-
- /*
- * Save program counter of <expression> part for
- * "jump to top of loop" code to be generated later.
- */
- p = Opsp;
- getoken();
- expression();
- /*
- * Save operator stack pointer of "jump if top of stack is zero"
- * code (break out of loop code). This is pushed onto a stack
- * so that it will be available for future "break" statements.
- */
- pushwhile( Opsp );
- generate( T_IF, -1 );
- /*
- * Parse the <statement> part, then generate code to jump back to
- * top of loop.
- */
- statement();
-
- generate( T_WHILE, p );
- /*
- * Backpatch "jump if zero" opcode generated above.
- */
- Opstk[ popwhile() ].o_value = Opsp;
- }
-
- dobreak()
- {
- /*
- * Parse a "break" statement. Generate code to push a zero onto
- * stack, then jump to the loop end test at top of loop. This test
- * will find a zero on the stack and jump to the end of the loop.
- */
- getoken();
- generate( T_CONST, 0 );
- generate( T_WHILE, pushwhile( popwhile() ) );
- }
-
- expression()
- {
- /*
- * Parse an expression. Expressions have the following syntax:
- * <expression> := <primary> <operator> <primary>
- * so the first thing to look for is a primary.
- */
- int lvalue;
- char notempty;
-
- /*
- * Check if end of expression first
- */
-
- if ( endofexpr() )
- return 0;
- else
- {
- notempty = 1; /* assume not the empty expression: "()" */
- if ( !(lvalue = primary()) )
- err( "bad expression" );
- else if ( lvalue == 2 )
- notempty = 0; /* it was the expression "()" */
- else if ( endofexpr() )
- {
- /*
- * The <primary> was an lvalue (variable reference)
- * and the stack will contain its address. Generate
- * code to load an integer from that address.
- */
- if ( lvalue < 0 )
- generate( T_POINT, 0 );
- }
- else
- op_prim( 0, lvalue );
- }
- /*
- * Return TRUE if it's an empty expression
- */
- return notempty;
- }
-
- endofexpr()
- {
- /*
- * Return TRUE if current Token marks end of an expression
- */
- return Eol || Error ||
- Token==T_RPAREN || Token==T_LBRACE ||
- Token==T_RBRACE || Token==T_SEMICOLON;
- }
-
- op_prim( precedence, lvalue )
- int precedence; /* precedence of current <operator> */
- int lvalue; /* type of current <primary>: -1 => lvalue */
- /* 0 => no <primary> (error) */
- /* 1 => rvalue */
- {
- /*
- * Parse the <operator> <primary> part of an expression.
- * "precedence" is the PREVIOUS <operator>'s precedence level
- * (0=low, +n=high).
- */
- char tkn;
- int pr, lv;
-
- /*
- * Loop until end of <expression> is found
- */
- while ( ! endofexpr() )
- {
- /*
- * Get the precedence level of current <operator> ("pr").
- * If it is greater than previous operator ("precedence"),
- * get the next <primary> and do another <operator> <primary>
- * NOTE: For left-to-right associativity, the condition
- * pr > precedence
- * must be true. for right-to-left associativity,
- * pr >= precedence
- * must be true (assignment operator only).
- */
-
- if ( !(pr = binop( Token )) )
- {
- /*
- * Found two (possibly) consecutive primaries.
- */
- err( "missing operator" );
- break;
- }
-
- if (
- (pr>precedence && pr>0) ||
- (Token==T_ASSIGN && pr>=precedence)
- )
- {
- if ( Token == T_ASSIGN )
- {
- if ( lvalue > 0 )
- err( "= needs and lvalue" );
- }
- else if ( lvalue < 0 )
- generate( T_POINT, 0 );
-
- /*
- * Save the operator token and do a primary.
- */
- tkn = Token;
- getoken();
- if ( ! (lv = primary()) )
- err( "missing operand" );
- /*
- * Now look at the next operator. If its precedence
- * is greater than this one ("tkn" above), generate
- * code for it BEFORE this one.
- */
- lvalue = op_prim( pr, lv );
-
- if ( Token != T_ASSIGN && lvalue < 0 )
- {
- /*
- * Next operator is not the assignment op.
- * and the current <primary> is an lvalue,
- * therefore generate a "load from address
- * on top of stack" instruction.
- */
- generate( T_POINT, 0 );
- /*
- * This makes it an rvalue now.
- */
- lvalue = 1;
- }
- else if ( tkn!=T_ASSIGN && Token==T_ASSIGN )
- {
- /*
- * YEECH! this is the only way I know of to
- * detect errors like: a+b=c
- */
- err( "= needs an lvalue" );
- }
-
- /*
- * Generate the instruction for the current operator.
- */
- if ( tkn!=T_COMMA )
- generate( tkn, 0 );
- }
- else
- break;
- }
-
- return lvalue;
- }
-
- primary()
- {
- /*
- * Parse a primary. Primaries have the following syntax:
- * <primary> := <constant> |
- * '(' <expression> ')' |
- * <unary op> <primary> |
- * <function> <primary>
- */
- int rtn, val, savcommas, needparen;
-
- /*
- * Return value:
- * -1 => the <primary> is an lvalue
- * 0 => not a <primary> (usually end of expr or syntax error)
- * 1 => the <primary> is an rvalue
- * 2 => the <primary> is the empty expression "()"
- */
- rtn = 1;
-
- switch ( Token )
- {
- case T_ADDR: /* address operator */
- getoken();
- if ( Token != T_SYMBOL )
- err( "@ not followed by a variable" );
- else
- {
- Token = T_CONST;
- Value = &Symbols[ Value ];
- }
- goto const;
- case T_SYMBOL: /* a symbol */
- rtn = -1;
- case T_CONST: /* a constant */
- case T_STRING: /* a string constant */
- ;
- const:
- generate( Token, Value );
- getoken();
- break;
- case T_LPAREN: /* a parenthesized expression */
- if ( getoken() == T_RPAREN )
- rtn = 2; /* special empty expression: () */
- else
- expression();
- if ( Token != T_RPAREN )
- {
- err( "missing ')'" );
- rtn = 0;
- }
- else
- getoken();
- break;
- case T_SUB: /* unary - */
- /*
- * The lexical analyzer is not smart enough to recognize
- * unary operators (+ and -), that's why we have to do
- * it here
- */
- getoken();
- expression();
- generate( T_NEG, 0 );
- break;
- case T_NOT: /* unary ~ */
- getoken();
- expression();
- generate( T_NOT, 0 );
- break;
- case T_ADD: /* unary + */
- getoken();
- expression();
- break;
- case T_LNOT: /* unary ! */
- getoken();
- expression();
- generate( T_LNOT, 0 );
- break;
- case T_FUNC: /* built-in function */
- val = Value;
- /*
- * Keep track of number of arguments pushed onto stack...
- */
- savcommas = Commas;
- Commas = needparen = 0;
- if ( getoken() == T_LPAREN )
- {
- getoken();
- needparen = 1;
- }
- if ( !expression() )
- --Commas; /* found the empty expression "()" */
-
- if ( needparen )
- {
- if ( Token!=T_RPAREN )
- err( "missing ')'" );
- getoken();
- }
- /*
- * set # of arguments
- */
- generate( T_COMMA, Commas+1 );
- generate( T_FUNC, val );
- Commas = savcommas;
- break;
- default:
- /*
- * Not a primary
- */
- rtn = 0;
- }
- return rtn;
- }
-
- binop( op )
- char op;
- {
- /*
- * Determine if "op" is a binary operator and return its
- * precedence level if so. If not, return 0.
- */
- switch ( op )
- {
- case T_COMMA:
- return 1;
- case T_ASSIGN:
- return 2;
- case T_IOR:
- return 3;
- case T_XOR:
- return 4;
- case T_AND:
- return 5;
- case T_LT:
- case T_GT:
- case T_LE:
- case T_GE:
- case T_EQ:
- case T_NE:
- return 6;
- case T_LAND:
- case T_LIOR:
- return 7;
- case T_SHL:
- case T_SHR:
- return 8;
- case T_ADD:
- case T_SUB:
- return 9;
- case T_MUL:
- case T_DIV:
- case T_MOD:
- return 10;
- case T_NOT:
- case T_LNOT:
- return 11;
- }
- return 0;
- }
-
- generate( tkn, val )
- char tkn;
- {
- /*
- * Push the given token and value onto the Operator/Operand stack.
- */
- if ( Opsp < MAXOPS )
- {
- Opstk[ Opsp ].o_token = tkn;
- Opstk[ Opsp ].o_value = val;
- #ifdef DEBUG
- if ( Debug )
- printf( "+%3d: %c %d\n", Opsp, tkn, val );
- #endif
- ++Opsp;
- }
- else
- err( "program too long" );
- }
-
- pushif( n )
- {
- if ( Ifsp < MAXIFS )
- Ifstk[ Ifsp++ ] = n;
- else
- err( "too many nested 'if's" );
- return n;
- }
-
- popif()
- {
- if ( Ifsp )
- return Ifstk[ --Ifsp ];
- err( "mismatched 'else'" );
- }
-
- pushwhile( n )
- {
- if ( Whsp < MAXWHILES )
- Whstk[ Whsp++ ] = n;
- else
- err( "too many nested 'while's" );
- return n;
- }
-
- popwhile()
- {
- if ( Whsp )
- return Whstk[ --Whsp ];
- err( "'break' not inside a 'while'" );
- }
-
- /*************************************************************
- * EXPRESSION EVALUATOR *
- **************************************************************/
- /*
- * NOTE: The comments make reference to "lvalues" and "rvalues". These
- * are attributes of <primaries> (primaries, for the layman, are things
- * like constants and variables, and parenthesized expressions. If you
- * don't know what an expression is, you shouldn't be a reading this!).
- * If a <primary> is an "lvalue", it means that it can usually be found on
- * LEFT-HAND side of an assignment operator. "rvalues" can only be found
- * on the RIGHT-HAND side of an assignment. Simply stated, only things like
- * variables can be used as both "lvalues" and "rvalues", whereas things
- * like constants and parenthesized expressions can only be "rvalues" since
- * it wouldn't make sense to say: 12 = 5.
- */
- evaluate()
- {
- /*
- * Evaluate an expression by popping operators and operands
- * from the Operator/Operand stack and performing each indicated
- * operation.
- */
- int val, *ip, i;
- char op;
-
- for ( Opptr=0; Opptr<Opsp; ++Opptr )
- {
- op = Opstk[ Opptr ].o_token;
- val = Opstk[ Opptr ].o_value;
-
- /*
- * Stop program if ^C is entered.
- */
- if ( bios( 2, 0 ) && getkey()==3 )
- break;
- #ifdef DEBUG
- if ( Debug )
- {
- printf( "-%3d: %c %d:", Opptr, op, val );
- for ( i=0; i<Valsp; ++i )
- printf( " %d", Valstk[ i ] );
- newline();
- }
- #endif
- switch ( op )
- {
- case T_CONST:
- case T_STRING:
- push( val );
- break;
- case T_SYMBOL:
- /*
- * Push the address of a variable
- */
- push( &Symbols[ val ] );
- break;
- case T_POINT:
- /*
- * Fetch an integer from address on top of stack.
- * This usually follows a T_SYMBOL when the symbol
- * is not being used as an "lvalue".
- */
- ip = pop();
- push( *ip );
- break;
- case T_IF:
- /*
- * Jump to the program line # given by operand
- * if top of stack is zero.
- */
- if ( !pop() )
- Opptr = val - 1;
- break;
- case T_WHILE:
- /*
- * Jump to the program line # given by operand
- */
- Opptr = val - 1;
- break;
- case T_POP:
- /*
- * Pop the stack. Usually follows an <expression>
- */
- pop();
- break;
- case T_COMMA:
- /*
- * Set # of arguments on stack
- */
- Commas = val;
- break;
- case T_FUNC:
- /*
- * Execute a built-in function
- */
- (*Functab[ Opstk[ Opptr ].o_value ].f_addr)();
- break;
- case T_ASSIGN:
- /*
- * Assignment operator: The item on top of stack is
- * the "rvalue", second on stack is the "lvalue"
- * (an address where to store the "rvalue"). The
- * "rvalue" gets pushed back on top of the stack.
- */
- val = pop();
- ip = pop();
- push( *ip = val );
- break;
- case T_NOT:
- TOS = ~TOS;
- break;
- case T_LNOT:
- TOS = !TOS;
- break;
- case T_NEG:
- TOS = -TOS;
- break;
- default:
- /*
- * All others are binary operators.
- */
- val = pop();
- switch ( op )
- {
- case T_ADD:
- TOS += val;
- break;
- case T_SUB:
- TOS -= val;
- break;
- case T_MUL:
- TOS *= val;
- break;
- case T_DIV:
- TOS /= val;
- break;
- case T_MOD:
- TOS %= val;
- break;
- case T_LT:
- TOS = TOS < val;
- break;
- case T_GT:
- TOS = TOS > val;
- break;
- case T_LE:
- TOS = TOS <= val;
- break;
- case T_GE:
- TOS = TOS >= val;
- break;
- case T_EQ:
- TOS = TOS == val;
- break;
- case T_NE:
- TOS = TOS != val;
- break;
- case T_SHL:
- TOS = TOS << val;
- break;
- case T_SHR:
- TOS = TOS >> val;
- break;
- case T_AND:
- TOS &= val;
- break;
- case T_XOR:
- TOS ^= val;
- break;
- case T_IOR:
- TOS |= val;
- break;
- case T_LAND:
- TOS = TOS && val;
- break;
- case T_LIOR:
- TOS = TOS || val;
- break;
- default:
- err( "parser error" );
- }
- }
- }
- }
-
- push( val )
- {
- if ( Valsp >= MAXVALS )
- err( "stack overflow" );
- return Valstk[ Valsp++ ] = val;
- }
-
- pop()
- {
- if ( --Valsp < 0 )
- Valsp = 0;
- return Valstk[ Valsp ];
- }
-
- /*************************************************************
- * BUILT-IN FUNCTIONS *
- **************************************************************/
- /*
- * NOTE: All functions expect the correct number of arguments on the
- * stack. These arguments are removed and exactly one argument is left in
- * their place. Thus, a built-in function is a transform that results in
- * a single rvalue.
- */
- f_printf()
- {
- /*
- * usage: printf( a0, a1, ... a9 )
- * does: do a formatted print, a la printf()
- * stacks: # of arguments printed
- */
- int a[ 10 ];
-
- getargs( 10, a );
- push( printf(a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9]) );
- }
-
- f_base()
- {
- /*
- * usage: base( n )
- * does: sets output number base
- * stacks: the argument n
- */
- int n;
-
- getargs( 1, &n );
-
- switch ( n )
- {
- case 8:
- Ofmt = "0%o";
- break;
- case 16:
- Ofmt = "0x%x";
- break;
- case 10:
- default:
- Ofmt = "%d";
- break;
- }
- push( n );
- }
-
- f_run()
- {
- /*
- * usage: run( s )
- * does: chain to program in filename pointed to by "s". If "s"
- * not given, executes source already in program buffer.
- * stacks: 1 if successful, 0 otherwise
- */
- char *s;
-
- if ( getargs( 1, &s ) )
- {
- Commas = 1;
- push( s );
- if ( !f_load() )
- {
- push( 0 );
- return;
- }
- }
-
- reset();
- *Line = 0;
- Source = 1;
- getoken();
- while ( Source )
- statement();
- /*
- * This function was called from evaluate(), so
- * remember to back up p-code pointer by one.
- */
- --Opptr;
- }
-
- f_nl()
- {
- /*
- * usage: nl()
- * does: outputs a newline to CON:
- * stacks: a newline character (0x0a)
- */
- getargs( 0, 0 );
- newline();
- push( '\n' );
- }
-
- f_putn()
- {
- /*
- * usage: putn( n )
- * does: prints numeric constant in the current number base
- * stacks: the number
- */
- int n;
-
- getargs( 1, &n );
- printf( Ofmt, n );
- push( n );
- }
-
- f_getn()
- {
- /*
- * usage: getn( v )
- * does: reads a number into the address at "v" (assumed to be
- * a variable). If "v" is not given, leaves number on stack.
- * stacks: number read
- */
- int *ip, n;
- char buf[ 128 ];
-
- gets( buf );
- n = atoi( buf );
-
- if ( getargs( 1, &ip ) )
- push( *ip = n );
- else
- push( n );
- }
-
- f_debug()
- {
- /*
- * usage: debug( v )
- * does: sets/resets the interpreter's debug flag, depending on v
- * stacks: v
- */
- int v;
-
- getargs( 1, &v );
- #ifdef DEBUG
- Debug = v;
- #endif
- push( v );
- }
-
- f_new()
- {
- /*
- * Erase the entire program buffer by freeing up all memory
- */
- getargs( 0, 0 );
- new();
- push( 0 );
- }
-
- new()
- {
- for ( Progptr=0; Progptr<Progtop; ++Progptr )
- free( Prog[ Progptr ] );
- Progptr = Progtop = 0;
- }
-
- f_load()
- {
- char *file, iobuf[ BUFSIZ ], rtn;
-
- if ( !getargs( 1, &file ) )
- file = Filenm;
-
- if ( *file && fopen( file, iobuf ) != -1 )
- {
- rtn = 1;
- new();
- while ( fgets( Line, iobuf ) )
- {
- Line[ strlen( Line ) - 1 ] = 0;
- if ( !makline( Progtop++, Line ) )
- {
- puts( "file too big\n" );
- rtn = 0;
- break;
- }
- }
- fclose( iobuf );
- }
- else
- {
- puts( "file not found\n" );
- rtn = 0;
- }
-
- if ( rtn )
- strcpy( Filenm, file );
-
- push( rtn );
-
- return rtn;
- }
-
- f_save()
- {
- char *file, iobuf[ BUFSIZ ], rtn;
- int i;
-
- if ( !getargs( 1, &file ) )
- file = Filenm;
-
- if ( *file && fcreat( file, iobuf ) != -1 )
- {
- for ( i=0; i<Progtop; ++i )
- {
- fputs( Prog[ i ], iobuf );
- putc( '\n', iobuf );
- }
- putc( 26, iobuf );
- fclose( iobuf );
- rtn = 1;
- }
- else
- {
- puts( "file not created\n" );
- rtn = 0;
- }
-
- if ( rtn )
- strcpy( Filenm, file );
-
- push( rtn );
-
- return rtn;
- }
-
- f_edit()
- {
- /*
- * Program buffer editor.
- */
- char *cp, col, lastcol;
- int i, c;
-
- if ( getargs( 1, &i ) )
- Progptr = i - 1;
- push( i );
- /*
- * Initialize: do some bounds checking on current program line ptr,
- * and redraw the current line.
- */
- ;start:
- col = 0;
- if ( !Progtop )
- {
- /*
- * There's always one blank line at the end of the buffer.
- * Therefore, we only need a line INSERT command, never an
- * APPEND...
- */
- Progptr = 0;
- addline( "" );
- }
- else if ( Progptr && Progptr >= Progtop )
- Progptr = Progtop - 1;
- else if ( Progptr < 0 )
- Progptr = 0;
-
- redraw:
- newline();
- fmtlno( Progptr );
- puts( cp = Prog[ Progptr ] );
-
- lastcol = strlen( cp );
- if ( col > lastcol )
- col = lastcol;
-
- fmtlno( Progptr );
- for ( i=0; i<col; ++i )
- putchar( cp[i] );
-
-
- /*
- * Command loop
- */
- for ( ;; )
- {
- switch ( c = getkey() )
- {
- case '\r': /* exit */
- case '\n':
- goto done;
- case 5: /* up */
- if ( Progptr )
- {
- --Progptr;
- goto start;
- }
- break;
- case 24: /* down */
- if ( Progptr < Progtop-1 )
- {
- ++Progptr;
- goto start;
- }
- break;
- case 19: /* left */
- case 8:
- if ( col )
- {
- putchar( '\b' );
- --col;
- }
- break;
- case 4: /* right */
- if ( col < lastcol )
- putchar( cp[ col++ ] );
- break;
- case 23: /* redraw window */
- newline();
- newline();
- if ( (i=Progptr-11) < 0 )
- i = 0;
- for ( c=i; c<i+22 && c<Progtop; ++c )
- fmtline( c );
- goto redraw;
- case 22: /* insert line mode */
- newline();
- newline();
- for ( ;; )
- {
- fmtlno( Progptr );
- if ( !gets( Line ) )
- break;
- if ( !insline( Progptr++, Line ) )
- break;
- }
- goto start;
- case 3: /* insert character mode */
- if ( Progptr < Progtop-1 )
- {
- for ( i=0; i<col; ++i )
- Line[i] = cp[i];
- gets( &Line[i] );
- strcat( Line, &cp[i] );
- free( cp );
- makline( Progptr, Line );
- goto redraw;
- }
- break;
- case 25: /* delete line */
- if ( Progptr < Progtop-1 )
- {
- delline( Progptr );
- goto start;
- }
- break;
- case 2: /* delete character */
- for ( i=col; i<lastcol; ++i )
- cp[i] = cp[i+1];
- goto redraw;
- default:
- if ( ' '<=c && c<='~' && col < lastcol )
- putchar( cp[ col++ ] = c );
- break;
- }
- }
- done:
- newline();
- }
-
- f_list()
- {
- int n[2], i;
-
- n[0] = 1; n[1] = Progtop;
- getargs( 2, n );
-
- puts( Filenm );
- newline();
-
- for ( i=n[0]-1; i<n[1]; ++i )
- fmtline( i );
- push( 0 );
- }
-
- f_stop()
- {
- int n;
-
- getargs( 1, &n );
- Opptr = Opsp;
- push( n );
- }
-
- getargs( n, ip )
- int *ip;
- {
- /*
- * Remove items from the Valstk and adjust stackptr.
- */
- int argc;
-
- if ( Commas > n )
- {
- /*
- * More arguments on stack than expected - remove excess
- */
- while ( Commas-- > n )
- pop();
- }
- else if ( Commas < n )
- {
- /*
- * Less arguments than expected - reduce n
- */
- n = Commas;
- }
-
- argc = 0;
- while ( n-- )
- {
- ++argc;
- ip[ n ] = pop();
- }
- return argc;
- }
-
- /*************************************************************
- * PROGRAM BUFFER MANIPULATION ROUTINES *
- *************************************************************/
- makline( lno, line )
- char *line;
- {
- /*
- * Copy the string at "line" into the program buffer at "lno".
- * A block of memory will be allocated for the new string.
- */
- char *cp;
-
- if ( cp = Prog[ lno ] = malloc(strlen(line) + 1) )
- {
- strcpy( cp, line );
- return 1;
- }
- return 0;
- }
-
- addline( line )
- char *line;
- {
- /*
- * Add the string at "line" to the end of the program buffer.
- */
- if ( Progtop >= MAXLINES )
- return 0;
-
- if ( makline( Progtop, line ) )
- {
- ++Progtop;
- return 1;
- }
- return 0;
- }
-
- insline( lno, line )
- char *line;
- {
- /*
- * Insert the string, "line" before "lno" in the program buffer.
- */
- int i;
-
- if ( lno >= Progtop )
- return 0;
-
- if ( Progtop )
- {
- /*
- * There is at least one line in the buffer. First append
- * a new line to the end of the program buffer and duplicate
- * the last line.
- */
- i = Progtop;
- if ( i < MAXLINES )
- {
- ++Progtop;
- /*
- * Move all lines below "lno" down
- */
- while ( i-- > lno )
- Prog[ i+1 ] = Prog[ i ];
- /*
- * Free up the string at "lno" and create a new
- * line there.
- */
- return makline( lno, line );
- }
- else
- return 0;
- }
- else
- /*
- * Nothing in program buffer yet - append the new line.
- */
- return addline( line );
-
- return 1;
- }
-
- delline( lno )
- {
- char *cp;
- int i;
-
- if ( lno >= Progtop )
- return 0;
-
- /*
- * There is at least one line in the buffer. First delete
- * the line at "lno" in the program buffer.
- */
- free( Prog[ lno ] );
- /*
- * Then move all lines below "lno" up.
- */
- while ( ++lno < Progtop )
- Prog[ lno-1 ] = Prog[ lno ];
- --Progtop;
- return 1;
- }
-
- fmtline( n )
- {
- fmtlno( n );
- puts( Prog[ n ] );
- newline();
- }
-
- fmtlno( n )
- {
- printf( "\r%4d:", n+1 );
- }
-
- /*************************************************************
- * MISCELLANEOUS *
- **************************************************************/
- initialize()
- {
- /*
- * Initialization routine - for compilers that do not support
- * global variable initialization.
- */
-
- /*
- * initialize function table
- */
- Functab[0].f_name = "printf";
- Functab[0].f_addr = f_printf;
-
- Functab[1].f_name = "base";
- Functab[1].f_addr = f_base;
-
- Functab[2].f_name = "run";
- Functab[2].f_addr = f_run;
-
- Functab[3].f_name = "nl";
- Functab[3].f_addr = f_nl;
-
- Functab[4].f_name = "putn";
- Functab[4].f_addr = f_putn;
-
- Functab[5].f_name = "getn";
- Functab[5].f_addr = f_getn;
-
- Functab[6].f_name = "debug";
- Functab[6].f_addr = f_debug;
-
- Functab[7].f_name = "new";
- Functab[7].f_addr = f_new;
-
- Functab[8].f_name = "edit";
- Functab[8].f_addr = f_edit;
-
- Functab[9].f_name = "list";
- Functab[9].f_addr = f_list;
-
- Functab[10].f_name = "save";
- Functab[10].f_addr = f_save;
-
- Functab[11].f_name = "load";
- Functab[11].f_addr = f_load;
-
- Functab[12].f_name = "exit";
- Functab[12].f_addr = exit;
-
- Functab[13].f_name = "stop";
- Functab[13].f_addr = f_stop;
- /*
- * keyword lookup table
- */
- Keytab[0].k_name = "if";
- Keytab[0].k_value = T_IF;
-
- Keytab[1].k_name = "else";
- Keytab[1].k_value = T_ELSE;
-
- Keytab[2].k_name = "while";
- Keytab[2].k_value = T_WHILE;
-
- Keytab[3].k_name = "break";
- Keytab[3].k_value = T_BREAK;
- /*
- * string table
- */
- Strings = malloc( MAXSTRINGS );
- /*
- * display number radix
- */
- push( 10 );
- f_base();
- pop();
- }
-
- reset()
- {
- /*
- * Initialize parser variables
- */
- Opptr=Opsp=Valsp=Ifsp=Whsp=Level=Parens=Commas=Error=Progptr = 0;
- Eol = 1;
- }
-
- putresult( result )
- {
- /*
- * Print results of an expression in current output format
- */
- printf( Ofmt, result );
- newline();
- }
-
- prompt()
- {
- int i;
-
- for ( i=0; i<Level; ++i )
- putchar( '\t' );
- puts( "> " );
- }
-
- err( s )
- {
- /*
- * Display an error message
- */
- if ( ! Error )
- {
- /*
- * We're only interested in the first one encountered
- * on a line, since error recovery is non-existent.
- */
- if ( Source )
- fmtlno( Progptr );
- puts( s );
- newline();
- Error = 1;
- }
- }
-
- newline()
- {
- putchar( '\n' );
-
- }
-
- ishexdigit( c )
- char c;
- {
- return instr( c, "0123456789abcdefABCDEF" );
- }
-
- isoctdigit( c )
- char c;
- {
- return instr( c, "01234567" );
- }
-
- instr( c, s )
- char c, *s;
- {
- /*
- * Return TRUE if the character "c" is in the string "s"
- */
- while ( *s )
- if ( c == *s++ )
- return 1;
- return 0;
- }
-
- getkey()
- {
- /*
- * Get a key directly from keyboard
- */
- return bios( 3, 0 );
- }
-
- gets( s )
- char *s;
- {
- int i, c;
-
- i = 0;
- while ( i<79 )
- {
- switch ( c = getkey() )
- {
- case '\r':
- case '\n':
- newline();
- goto done;
- case '\t':
- for ( c=0; c<3 && i<79; ++c )
- putchar( s[ i++ ] = ' ' );
- break;
- case '\b':
- if ( i )
- {
- --i;
- puts( "\b \b" );
- }
- break;
- case 3:
- exit();
- case 4:
- Debug = !Debug;
- break;
- default:
- if ( ' '<=c && c<='~' )
- putchar( s[ i++ ] = c );
- }
- }
- done:
- s[ i ] = 0;
- return i;
- }